﻿using System;
using System.Collections.Generic;
using System.Threading;
using System.Linq;
using System.Text;
using Microsoft.WindowsAzure.StorageClient;
using Microsoft.Samples.ServiceHosting.StorageClient;
using System.IO;
using Ciripescu_WebRole.BL;
using EmailSender.Properties;
using Ciripescu_WebRole;
using Ciripescu_WebRole.Entities;
using System.Net.Mail;
using System.Diagnostics;
using Microsoft.WindowsAzure.Diagnostics;

namespace EmailSender
{
    public enum EventName
    {
        Information,
        Warning,
        Error
    }
    public class WorkerRole : Microsoft.WindowsAzure.ServiceRuntime.RoleEntryPoint
    {
        // TODO: de bagat la settings
        public int limitaCiripuri = 1000;

        public void WriteLog(EventName eventName, string message)
        {
            switch (eventName)
            {
                case EventName.Information:
                    System.Diagnostics.Trace.TraceInformation(message);
                    break;
                case EventName.Warning:
                    System.Diagnostics.Trace.TraceWarning(message);
                    break;
                case EventName.Error:
                    System.Diagnostics.Trace.TraceError(message);
                    break;
                default:
                    break;
            }
        }

        public override bool OnStart()
        {
            FirstRequestInitialization.ApplicationStartUponFirstRequest();
        
            DiagnosticMonitorConfiguration dConfig = DiagnosticMonitor.GetDefaultInitialConfiguration();
            
            dConfig.Logs.ScheduledTransferPeriod        = TimeSpan.FromMinutes(15);
            dConfig.Directories.ScheduledTransferPeriod = TimeSpan.FromMinutes(15);
            
            dConfig.WindowsEventLog.ScheduledTransferPeriod = TimeSpan.FromMinutes(15);
            dConfig.WindowsEventLog.DataSources.Add("Application!*");
            dConfig.WindowsEventLog.DataSources.Add("System!*");

            DiagnosticMonitor.Start("DiagnosticsConnectionString",dConfig);
            
            new Thread(new ThreadStart(ScheduledMailLoop)).Start();
            new Thread(new ThreadStart(CiripCacheCleaner)).Start();
            new Thread(new ThreadStart(UrgentMailLoop)).Start();

            System.Diagnostics.Trace.TraceInformation("Worker role start completed");
            return base.OnStart();
        }

        public override void Run()
        {
            
            System.Diagnostics.Trace.TraceInformation("Worker running");
            base.Run();
        }
        // TODO: testeaza daca merge:
        public void CiripCacheCleaner()
        {
            CiripCacheManager cm = new CiripCacheManager();
            
            while (true)
            {
                try
                {
                    // scoate lista cu toate ciripurile din cache si daca depasesc limita sterge ce este deasupra ei.
                    List<CiripCache> toate = cm.GetAllItems().ToList();
                    if (toate.Count > limitaCiripuri)
                    {
                        for (int i = limitaCiripuri; i < toate.Count; i++)
                            cm.DeleteWithNoCommit(toate[i]);
                        cm.Commit();
                    }
                }
                catch (Exception ex)
                {
                    // taci si inghite
                }
                Thread.Sleep(new TimeSpan(2, 0, 0)); // 2 ore
            }
        }

        
        public void ScheduledMailLoop()
        {
            StorageAccountInfo account = StorageAccountInfo.GetDefaultQueueStorageAccountFromConfiguration();
            QueueStorage service = QueueStorage.Create(account);
            MessageQueue queue = service.GetQueue(MailQueue.ScheduledQueueName);

            //if(!queue.DoesQueueExist())
            //   RoleManager.WriteToLog("Critical", "Queue EmailToSend does not exist");
            InviteManager im = new InviteManager();
            DateTime lastEmail = new DateTime(2000, 1, 1);
            while (true)
            {              
                if (queue.DoesQueueExist())
                {

                    int nbEmails = queue.ApproximateCount();
                    if (nbEmails == 0)
                    {
                        //WriteLog(EventName.Information, "coada e goala - stau un minut (10 sec)");
                        //RoleManager.WriteToLog("Information", "coada e goala - stau un minut (10 sec)");
                        Thread.Sleep(10*1000); // un min
                        
                        continue;
                    }
                    // intervalul in care trebuie trimise mailurile ca sa fie repartizate uniform
                    double interval = (double)MailQueue.DeliveryTimeFactor / (double)nbEmails; // ore
                    TimeSpan ts = DateTime.Now.Subtract(lastEmail);
                    if (ts.TotalHours < interval)
                    {
                        Sleep(interval - ts.TotalHours);
                        continue;
                    }

                    var msg = queue.GetMessage(120);
                    WriteLog(EventName.Information, "Sunt " + nbEmails + " mesaje in coada si trebuie trimise la " + ts.ToString());
                    //RoleManager.WriteToLog("Information", "Sunt " + nbEmails + " mesaje in coada si trebuie trimise la " + ts.ToString());

                    if (msg != null)
                    {
                        
                        
                        var split = msg.ContentAsString().Split('/');
                        var mailType = split[0];
                        var sendTo = string.Format("{0}/{1}", split[1], split[2]); //name<email>

                        string body;
                        bool messageSent = true;

                        if (split[0] == MailType.Invite.ToString())
                        {
                            string rcptName = split[1];
                            string rcptEmail = split[2];
                            string senderName = split[3];
                            string senderEmail=split[4];

                            // noteaza faptul ca userul a fost invitat
                            Invite inv = im.GetItem(rcptEmail);
                            if (inv!=null)
                                if (inv.noMoreEmails)
                                {
                                    queue.DeleteMessage(msg);
                                    continue;
                                }

                            body = Resources.InvitationMessage;//obfuscatedSenderEmail
                            while (body.Contains("$obfuscatedSenderEmail")) body = body.Replace("$obfuscatedSenderEmail", Utils.ObfuscateEmail(senderEmail));
                            while (body.Contains("$obfuscatedRcptEmail")) body = body.Replace("$obfuscatedRcptEmail", Utils.ObfuscateEmail(rcptEmail));
                            while (body.Contains("$rcptName")) body = body.Replace("$rcptName", rcptName);
                            
                            if (inv!=null)
                                while (body.Contains("$key")) body = body.Replace("$key", inv.RowKey);

                            try
                            {
                                // trimite mailul doar daca nu a mai primit o invitatie recent
                                if (inv != null)
                                {
                                    if (inv.LastInvite < DateTime.Now.AddDays(-7))
                                    {
                                        MailSender.Send(sendTo, Resources.InvitationSubject.Replace("$senderName", senderName), body);

                                        if (!inv.UsersWhoInvitedHim.Contains(senderEmail))
                                            inv.UsersWhoInvitedHim += senderEmail + " ";
                                        inv.NbOfTimes++;
                                        inv.LastInvite = DateTime.Now;
                                        im.Save(inv);
                                    }
                                }
                                else
                                {
                                    MailSender.Send(sendTo, Resources.InvitationSubject.Replace("$senderName", senderName), body);
                                    Utils.WriteLog(EventLogName.Information, "Emailul " + sendTo + " nu apare in lista de invite dar tocmai i-am trimis o invitatie.");
                                }
                            }
                            catch (SmtpFailedRecipientsException ex)
                            {
                                messageSent = true; // abort sending
                                Utils.WriteLog(EventLogName.Information, "Exceptie la trimitere mail catre " + sendTo + ". Cod: " + ex.StatusCode + ". Message: "+ex.Message+". StackTrace: " + ex.StackTrace);
                            }
                            catch (Exception ex)
                            {
                                //RoleManager.WriteToLog("Critical", ex.ToString());
                                WriteLog(EventName.Error, ex.ToString());
                                messageSent = false;
                            }
                        }
                        else if (split[0] == MailType.UrmaritorNou.ToString())
                        {
                            string rcptName = split[1];
                            string rcptEmail = split[2];
                            string senderName = split[3];
                            string senderEmail = split[4];
                            string rcptUser = split[5];
                            string senderUser = split[6];

                            body = Resources.UrmaritorNouMessage.Replace("$rcptName", rcptName);
                            while (body.Contains("$rcptName")) body = body.Replace("$rcptName", rcptName);
                            while (body.Contains("$rcptUser")) body = body.Replace("$rcptUser", rcptUser);
                            while (body.Contains("$senderName")) body = body.Replace("$senderName", senderName);
                            while (body.Contains("$senderUser")) body = body.Replace("$senderUser", senderUser);
                            try
                            {
                                MailSender.Send(sendTo, Resources.UrmaritorNouSubject.Replace("$senderName", senderName), body);
                            }
                            catch (SmtpFailedRecipientsException ex)
                            {
                                messageSent = true; // abort sending
                                Utils.WriteLog(EventLogName.Information, "Exceptie la trimitere mail catre " + sendTo + ". Cod: " + ex.StatusCode + ". Message: " + ex.Message + ". StackTrace: " + ex.StackTrace);
                            }
                            catch (Exception ex)
                            {
                                //RoleManager.WriteToLog("Critical", ex.ToString());
                                WriteLog(EventName.Error, ex.ToString());
                                messageSent = false;
                            }
                        }
                        if (messageSent)
                        {
                            queue.DeleteMessage(msg);
                            lastEmail = DateTime.Now;
                        }
                    }
                    else
                        Thread.Sleep(1000);
                }
            }
        }


        public void UrgentMailLoop()
        {
            StorageAccountInfo account = StorageAccountInfo.GetDefaultQueueStorageAccountFromConfiguration();
            QueueStorage service = QueueStorage.Create(account);
            MessageQueue queue = service.GetQueue(MailQueue.UrgentQueueName);
            
            while (true)
            {
                if (queue.DoesQueueExist())
                {

                    var msg = queue.GetMessage(120);

                    if (msg != null)
                    {
                        WriteLog(EventName.Information, string.Format("Message to send: '{0}'", msg.ContentAsString()));
                        //RoleManager.WriteToLog("Information",
                         //   string.Format("Message to send: '{0}'", msg.ContentAsString()));
                        var split = msg.ContentAsString().Split('/');
                        var mailType = split[0];
                        var sendTo = string.Format("{0}/{1}", split[1], split[2]); //name<email>

                        string body;
                        bool messageSent = true;
                        
                        if (split[0] == MailType.Welcome.ToString())
                        {
                            body = Resources.WelcomeMessage.Replace("$rcptName", split[1]);
                            try
                            {
                                MailSender.Send(sendTo, Resources.WelcomeSubject, body);
                            }
                            catch (SmtpFailedRecipientsException ex)
                            {
                                messageSent = true; // abort sending
                                Utils.WriteLog(EventLogName.Information, "Exceptie la trimitere mail catre " + sendTo + ". Cod: " + ex.StatusCode + ". Message: " + ex.Message + ". StackTrace: " + ex.StackTrace);
                            }
                            catch (Exception ex)
                            {
                                //RoleManager.WriteToLog("Critical", ex.ToString());
                                WriteLog(EventName.Error, ex.ToString());
                                messageSent = false;
                            }
                        }
                        else if (split[0] == MailType.ReportToDevTeam.ToString())
                        {
                            body = split[3];
                            // TODO: pune un base64 encode/decode adevarat
                            while (body.Contains("[slash]"))
                                body = body.Replace("[slash]", "/");
                            try
                            {
                                MailSender.Send(sendTo, "Automatic Bug Report pt ciripesc.ro", body);
                            }
                            catch (SmtpFailedRecipientsException ex)
                            {
                                messageSent = true; // abort sending
                                Utils.WriteLog(EventLogName.Information, "Exceptie la trimitere mail catre " + sendTo + ". Cod: " + ex.StatusCode + ". Message: " + ex.Message + ". StackTrace: " + ex.StackTrace);
                            }
                            catch (Exception ex)
                            {
                                // logheaza eroare critica ca nu s-a putut loga un log critic :P
                               // RoleManager.WriteToLog("Critical", ex.ToString());
                                WriteLog(EventName.Error, ex.ToString());
                                messageSent = false;
                            }
                        }
                        else if (split[0] == MailType.ResetPass.ToString())
                        {
                            //TODO
                            //string msg = string.Format("{0}/{1}/{2}/{3}", MailType.ResetPass, rcptName, rcptEmail, confirmationKey);
                            body = Resources.EmailResetPass.Replace("<username>", split[1]);
                            body = body.Replace("<confirm>", split[3]);
                            try
                            {
                                MailSender.Send(sendTo, Resources.ResetPassSubj, body);
                            }
                            catch (SmtpFailedRecipientsException ex)
                            {
                                messageSent = true; // abort sending
                                Utils.WriteLog(EventLogName.Information, "Exceptie la trimitere mail catre " + sendTo + ". Cod: " + ex.StatusCode + ". Message: " + ex.Message + ". StackTrace: " + ex.StackTrace);
                            }
                            catch (Exception ex)
                            {
                                //RoleManager.WriteToLog("Critical", ex.ToString());
                                WriteLog(EventName.Error, ex.ToString());
                                messageSent = false;
                            }
                        }
                            else if (split[0] == MailType.Invite.ToString())
                            {
                                //RoleManager.WriteToLog("Critical", "Mesajele de Invite nu trebuiau sa ajunga in coada pt critice!");
                                WriteLog(EventName.Error, "Mesajele de Invite nu trebuiau sa ajunga in coada pt critice!");
                            }
                            else if (split[0] == MailType.UrmaritorNou.ToString())
                            {
                                WriteLog(EventName.Error, "Mesajele de UrmaritorNou nu trebuiau sa ajunga in coada pt critice!");
                                // RoleManager.WriteToLog("Critical", "Mesajele de UrmaritorNou nu trebuiau sa ajunga in coada pt critice!");
                            }

                        if (messageSent)                        
                            queue.DeleteMessage(msg);                            
                    }
                    else
                        Thread.Sleep(10000); // 10 secunde
                }
            }
        }

        private void Sleep(double maximumHoursToSleep)
        {            
            int ms = (int)(maximumHoursToSleep * 60 * 60 * 1000);            
            if (maximumHoursToSleep > 1) // s-a cerut mai mult de o ora pauza
                ms = 2 * 60 * 1000; // dormi 2 min si mi verifica dupa            
            else if (maximumHoursToSleep > 0.1) // s-a cerut peste 6 minute pauza
                ms = 20 * 1000; // dormi 20 secunde
            else if (maximumHoursToSleep > 0.006) // dormi mai mult de 20 de sec
                ms = 3 * 1000; // o secunda
            else ms = ms/2;

            if (ms>50)
                Thread.Sleep(ms);
        }

        
        //public override RoleStatus GetHealthStatus()
        //{
        //    // sunt curios ce se va intampla daca workerul ajunge varza dar el tot timpul returneza Healthy?
        //    ///in releasul de nov cred k metoda e deprecated
        //    return RoleStatus.Healthy;
        //}
        
    }
}
